package org.ysh.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.ysh.core.shiro.CustomeFormAuthenticationFilter;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Shiro 配置文件
 */
@Configuration
@Slf4j
public class ShiroConfig {

    /**
     * Shiro安全缓存，不然每次请求都会去走Realm 的doGetAuthorization方法去获得授权信息
     * @return
     */
    @Bean
    public EhCacheManager shiroCacheManager(){
        EhCacheManager shiroCacheManager = new EhCacheManager();
        shiroCacheManager.setCacheManagerConfigFile("classpath:shiro-ehcache.xml");
        return shiroCacheManager;
    }


    /**
     * 配置SecurityManager，这里基于Web环境的，所以实现类为DefaultWebSecurityManager
     *
     * @return
     */
    @Bean
    public SecurityManager securityManager(Realm customerRealm, CacheManager cacheManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(customerRealm);
        securityManager.setCacheManager(cacheManager);
        return securityManager;
    }

    /**
     * ShiroFilterFactoryBean 处理拦截资源文件问题。
     * 注意：单独一个ShiroFilterFactoryBean配置是会报错的，因为在
     * 初始化ShiroFilterFactoryBean的时候需要注入：SecurityManager
     *
     Filter Chain定义说明
     1、一个URL可以配置多个Filter，使用逗号分隔
     2、当设置多个过滤器时，全部验证通过，才视为通过
     3、部分过滤器可指定参数，如perms，roles
     *
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //设置SecurityManager
        bean.setSecurityManager(securityManager);

        Map<String, Filter> filters = new HashMap<String,Filter>();
        filters.put("authc",formAuthenticationFilter());
        bean.setFilters(filters);
        //拦截器
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
        filterChainDefinitionMap.put("/login*", "authc");//这里必须是login*，如果是login.html则是login.html*因为后面会有jsessionID之类的后缀，会影响到URL匹配到这个过滤器上
        filterChainDefinitionMap.put("/ApiTest/**", "anon");
        filterChainDefinitionMap.put("/bpmSso/**", "anon");
        filterChainDefinitionMap.put("/public/**", "anon");
        filterChainDefinitionMap.put("/favicon.ico", "anon");
        filterChainDefinitionMap.put("/login/captcha.png*", "anon");

        filterChainDefinitionMap.put("/logout", "logout");
        filterChainDefinitionMap.put("/", "authc");
        filterChainDefinitionMap.put("/**", "user");


        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        bean.setLoginUrl("/login");
        bean.setSuccessUrl("/index");
        bean.setUnauthorizedUrl("/403");

        log.error("Shiro 初始化完成...");
        return bean;
    }

    /**
     * 这个相当于执行了 SecurityUtils.setSecurityManager(securityManager)
     *
     * @param securityManager
     * @return
     */
    @Bean
    public MethodInvokingFactoryBean setSecurityManager(SecurityManager securityManager) {
        MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
        methodInvokingFactoryBean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
        methodInvokingFactoryBean.setArguments(securityManager);
        return methodInvokingFactoryBean;
    }

    /**
     * 自定义的FormAuthenticationFilter
     * @return
     */
    @Bean
    public FormAuthenticationFilter formAuthenticationFilter(){
        FormAuthenticationFilter filter = new CustomeFormAuthenticationFilter();
        filter.setSuccessUrl("/index");
        filter.setLoginUrl("/login");
        return filter;
    }

    /**
     * 负责shiroBean的生命周期
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 开启Shiro注解支持
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    /**
     * AOP支持以类作为代理
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator aop = new DefaultAdvisorAutoProxyCreator();
        aop.setProxyTargetClass(true);
        return aop;
    }

    /**
     * 解决每次重启后报错 Unable to execute 'doFinal' with cipher instance [javax.crypto.Cipher@138b80e8]
     * @return
     */
    @Bean
    public CookieRememberMeManager cookieRememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        simpleCookie.setMaxAge(259200000);
        cookieRememberMeManager.setCookie(simpleCookie);
        cookieRememberMeManager.setCipherKey(Base64.decode("6ZmI6I2j5Y+R5aSn5ZOlAA=="));
        return cookieRememberMeManager;
    }

    //需要在shiro配置文件中增加一个方法，用于thymeleaf和shiro标签配合使用
    @Bean
    public ShiroDialect getShiroDialect(){
        return new ShiroDialect();
    }

}
